/*
 * A concurrent Tcpspellingserver can accpet multiple client with threadpool.
 * Written by YUE CHI
 */
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * The concurrent TCP Spelling check server can accept multiple client checking.
 * 
 * @author YUE CHI
 * @version 1.0
 */
public class ConcurrentTCPSPellingServer extends AbstractServer
{

  /**
   * The default number of the thread pool.
   */
  public static final int NUMBER_OF_THREAD = 10;

  /**
   * The server socket of tcp server, need to be listened.
   */
  private final ServerSocket my_tcp_server_socket;

  /**
   * The thread number of current tcp connection.
   */
  private int my_threadcount = 1;

  /**
   * The thread pool of the server.
   */
  private final ExecutorService my_thread_pool;

  /**
   * The Concurrent TCP server can accept multiple client. Each time call for
   * another TCPSpellingServer.
   * 
   * @param the_args the input string form commandline
   * @param the_thread_number the current thread number
   * @throws SocketException socket problems
   */
  public ConcurrentTCPSPellingServer(final String[] the_args, 
                                 final int the_thread_number) throws SocketException
  {
    super(the_args);
    this.my_tcp_server_socket = getServerSocket(this.my_port);
    Log.out("Configuring for totally " + the_thread_number + " thread sessions");   
    // create a thread pool, with unbounded queue, that
    // has a maximum of num_threads active threads
    this.my_thread_pool = Executors.newFixedThreadPool(the_thread_number);
    for (;;)
    {
      try
      {
        this.excute();
      }
      catch (final IOException e)
      {
        Log.err("IO problem: " + e.getClass().getName());
        e.printStackTrace();
      }
    }
  }
  
  /**
   * Get the serversocket to be listened.
   * @param the_port  the port number
   * @return  the serversocket.
   */
  public ServerSocket getServerSocket(final int the_port)
  {
    ServerSocket temp = null;
    try
    {      
      temp = new ServerSocket(the_port);
      Log.out("Open on the port " + temp.getLocalPort() + 
              "\nInitialized network. Ready for queries.");
    }
    catch (final IOException e)
    {
      Log.err("Cannot initialize networks " + e);
      e.printStackTrace();
    }
    return temp;
    
  }

  /**
   * Submit the concurrent to the threadpool and start execute.
   * @throws IOException  io problems
   */
  public void excute() throws IOException
  {
    //Initial a new socket when listening.
    final Socket accpetsocket = this.my_tcp_server_socket.accept();
    final int tempcount = adjustcount();
    Log.out("Enqueued connection from " + accpetsocket.getInetAddress() + " to " +
            accpetsocket.getPort() + " as session " + tempcount);
    //Submit the current TCPSpellingServer to the thread pool.
    this.my_thread_pool.submit(new TCPSpellingServer(this, tempcount, accpetsocket));
    
  }

  /**
   * Sychronize the threadnumber.
   * @return the current threadnumber, each time +1.
   */
  private synchronized int adjustcount()
  {
    //Each time the threadcount+1, no matter whether the previous thread is shutdown.
    return this.my_threadcount++;
  }

  /**
   * Adjust the input array without the number.
   * @param the_args   the input string with thread number.
   * @return the input string array without the thread number.
   */
  public static String[] adjustarray(final String[] the_args)
  {
    //Before is <port><wordlist><threadnumber>
    //After is <port><wordlist>
    final String[] temp = new String[2];
    temp[0] = the_args[0];
    temp[1] = the_args[1];
    return temp;
  }
  
/**
 * Find the threadnumber from the input. It is the 3rd digit of the input string.
 * @param the_args the input string
 * @return the thread number.
 */
  public static int findthreadnumber(final String[] the_args)
  {
    int num = NUMBER_OF_THREAD;
    if (the_args.length > 2)
    {
      try
      {
        num = Integer.parseInt(the_args[2]);
      }
      catch (final NumberFormatException e)
      {
        //If we cannot load the thread number, the threadnumber = default number.
        num = NUMBER_OF_THREAD;
      }
    }
    return num;
  }

  /**
   * The main function of ConcurrentTCPSpellingServer.
   * @param the_args  the input String from the commandline.
   * @throws SocketException  if here is a socket problem.
   */
  public static void main(final String[] the_args) throws SocketException
  {
    final int num_threads = findthreadnumber(the_args);
    final String[] newline = adjustarray(the_args);
    final ConcurrentTCPSPellingServer server =
        new ConcurrentTCPSPellingServer(newline, num_threads);
   
    //The server never shutdown, no need to wait.
    
//    while (!server.my_thread_pool.isTerminated())
//    {
//      try
//      {
//        // highly unlikely that it will take more than 2 hour
//        // to do this computation
//        server.my_thread_pool.awaitTermination(2, TimeUnit.HOURS);
//      }
//      catch (final InterruptedException e)
//      {
//        System.err.println("unexpected interrupt awaiting termination");
//      }
//    }
  }

}
